package code.gateplugins.libsvm;

import gate.creole.ExecutionException;

import org.apache.log4j.Logger;

import libsvm.svm_parameter;

public class CommandLineParser {

	private static final Logger LOG = Logger.getLogger(CommandLineParser.class);

	private static void printHelp() {
		LOG.info("Usage: svm_train [options] training_set_file [model_file]\n"
				+ "options:\n"
				+ "-s svm_type : set type of SVM (default 0)\n"
				+ "	0 -- C-SVC		(multi-class classification)\n"
				+ "	1 -- nu-SVC		(multi-class classification)\n"
				+ "	2 -- one-class SVM\n"
				+ "	3 -- epsilon-SVR	(regression)\n"
				+ "	4 -- nu-SVR		(regression)\n"
				+ "-t kernel_type : set type of kernel function (default 2)\n"
				+ "	0 -- linear: u'*v\n"
				+ "	1 -- polynomial: (gamma*u'*v + coef0)^degree\n"
				+ "	2 -- radial basis function: exp(-gamma*|u-v|^2)\n"
				+ "	3 -- sigmoid: tanh(gamma*u'*v + coef0)\n"
				+ "	4 -- precomputed kernel (kernel values in training_set_file)\n"
				+ "-d degree : set degree in kernel function (default 3)\n"
				+ "-g gamma : set gamma in kernel function (default 1/num_features)\n"
				+ "-r coef0 : set coef0 in kernel function (default 0)\n"
				+ "-c cost : set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1)\n"
				+ "-n nu : set the parameter nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5)\n"
				+ "-p epsilon : set the epsilon in loss function of epsilon-SVR (default 0.1)\n"
				+ "-m cachesize : set cache memory size in MB (default 100)\n"
				+ "-e epsilon : set tolerance of termination criterion (default 0.001)\n"
				+ "-h shrinking : whether to use the shrinking heuristics, 0 or 1 (default 1)\n"
				+ "-b probability_estimates : whether to train a SVC or SVR model for probability estimates, 0 or 1 (default 0)\n"
				+ "-wi weight : set the parameter C of class i to weight*C, for C-SVC (default 1)\n"
				+ "-v n : n-fold cross validation mode\n" + "-q : quiet mode (no outputs)\n");
	}

	private static double toDouble(String s) throws ExecutionException {
		double d = Double.parseDouble(s);
		
		if (Double.isNaN(d) || Double.isInfinite(d)) {
			throw new ExecutionException("NaN or Infinity in libsvm parameters!");
		}
		return d;
	}

	public static svm_parameter parseCommandLine(String argv[]) throws ExecutionException {
		int i;
		svm_parameter param = new svm_parameter();

		// default values
		param.svm_type = svm_parameter.C_SVC;
		param.kernel_type = svm_parameter.RBF;
		param.degree = 3;
		param.gamma = 0; // 1/num_features
		param.coef0 = 0;
		param.nu = 0.5;
		param.cache_size = 100;
		param.C = 1;
		param.eps = 1e-3;
		param.p = 0.1;
		param.shrinking = 1;
		param.probability = 0;
		param.nr_weight = 0;
		param.weight_label = new int[0];
		param.weight = new double[0];

		// parse options
		for (i = 0; i < argv.length; i++) {
			if (argv[i].charAt(0) != '-') {
				break;
			}
			if (++i >= argv.length) {
				printHelp();
				return null;
			}
			switch (argv[i - 1].charAt(1)) {
			case 's':
				param.svm_type = Integer.parseInt(argv[i]);
				break;
			case 't':
				param.kernel_type = Integer.parseInt(argv[i]);
				break;
			case 'd':
				param.degree = Integer.parseInt(argv[i]);
				break;
			case 'g':
				param.gamma = toDouble(argv[i]);
				break;
			case 'r':
				param.coef0 = toDouble(argv[i]);
				break;
			case 'n':
				param.nu = toDouble(argv[i]);
				break;
			case 'm':
				param.cache_size = toDouble(argv[i]);
				break;
			case 'c':
				param.C = toDouble(argv[i]);
				break;
			case 'e':
				param.eps = toDouble(argv[i]);
				break;
			case 'p':
				param.p = toDouble(argv[i]);
				break;
			case 'h':
				param.shrinking = Integer.parseInt(argv[i]);
				break;
			case 'b':
				param.probability = Integer.parseInt(argv[i]);
				break;
			case 'v':
				LOG.error("n-fold cross validation not supported. Ignoring!");
				break;
			case 'w':
				++param.nr_weight;
				int[] oldI = param.weight_label;
				param.weight_label = new int[param.nr_weight];
				System.arraycopy(oldI, 0, param.weight_label, 0, param.nr_weight - 1);

				double[] oldD = param.weight;
				param.weight = new double[param.nr_weight];
				System.arraycopy(oldD, 0, param.weight, 0, param.nr_weight - 1);

				param.weight_label[param.nr_weight - 1] = Integer.parseInt(argv[i - 1].substring(2));
				param.weight[param.nr_weight - 1] = toDouble(argv[i]);
				break;
			default:
				LOG.error("Unknown option: " + argv[i - 1] + " Ignoring!.");
			}
		}
		return param;
	}
}
